package com.night.icm.service.business.service;

import com.github.wenhao.jpa.PredicateBuilder;
import com.github.wenhao.jpa.Specifications;
import com.night.icm.common.exception.NoSuchDataException;
import com.night.icm.common.jpa.PageHelper;
import com.night.icm.common.night_enum.StudyStatus;
import com.night.icm.common.service.PageDataUtil;
import com.night.icm.common.utils.PageDataDTO;
import com.night.icm.common.utils.TermType;
import com.night.icm.model.business.StudentCourseGradeDTO;
import com.night.icm.model.business.StudentCourseGradeGatherDTO;
import com.night.icm.persistence.base.entity.CourseType;
import com.night.icm.persistence.base.entity.Student;
import com.night.icm.persistence.base.entity.Teacher;
import com.night.icm.persistence.business.entity.*;
import com.night.icm.persistence.business.reporsitory.*;
import com.night.icm.service.business.mapper.StudentCourseGradeGatherMapper;
import com.night.icm.service.business.mapper.StudentCourseGradeMapper;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * StudentCourseGradeServiceImpl
 *
 * @author Liao ZhiYong
 * @date 2020/2/15
 */
@Service
@Transactional(readOnly = true, rollbackFor = Exception.class)
public class StudentCourseGradeServiceImpl implements StudentCourseGradeService {

    private final StudentCourseGradeRepository studentCourseGradeRepository;
    private final StudentCourseGradeMapper studentCourseGradeMapper;
    private final StudentCourseGradeGatherService studentCourseGradeGatherService;
    private final StudentCourseGradeGatherRepository studentCourseGradeGatherRepository;
    private final StudentCourseGradeGatherMapper studentCourseGradeGatherMapper;
    private final StudentCourseRepository studentCourseRepository;
    private final StudentCreditRecordRepository studentCreditRecordRepository;
    private final AcademicProgressRepository academicProgressRepository;

    @Autowired
    public StudentCourseGradeServiceImpl(StudentCourseGradeRepository studentCourseGradeRepository, StudentCourseGradeMapper studentCourseGradeMapper, StudentCourseGradeGatherService studentCourseGradeGatherService, StudentCourseGradeGatherRepository studentCourseGradeGatherRepository, StudentCourseGradeGatherMapper studentCourseGradeGatherMapper, StudentCourseRepository studentCourseRepository, StudentCreditRecordRepository studentCreditRecordRepository, AcademicProgressRepository academicProgressRepository) {
        this.studentCourseGradeRepository = studentCourseGradeRepository;
        this.studentCourseGradeMapper = studentCourseGradeMapper;
        this.studentCourseGradeGatherService = studentCourseGradeGatherService;
        this.studentCourseGradeGatherRepository = studentCourseGradeGatherRepository;
        this.studentCourseGradeGatherMapper = studentCourseGradeGatherMapper;
        this.studentCourseRepository = studentCourseRepository;
        this.studentCreditRecordRepository = studentCreditRecordRepository;
        this.academicProgressRepository = academicProgressRepository;
    }

    /**
     * 分页查找学生课程成绩
     *
     * @param page        页号，从0开始
     * @param size        每页记录条数
     * @param sort        排序字段，例如：字段1,asc,字段2,desc
     * @param academyName 学院名称
     * @param className   班级名称
     * @param studentName 学生姓名
     * @param courseName  课程名称
     * @param teacherId   授课老师id
     * @param year        所属年度
     * @param termType    所属学期
     * @param isCorrect   是否批改
     * @return 学生课程成绩
     */
    @Override
    public PageDataDTO<StudentCourseGradeDTO> findOnePage(int page, int size, String sort, String academyName, String className, String studentName, String courseName, String teacherId, String year, TermType termType, Boolean isCorrect) {
        Specification<StudentCourseGrade> specification = ((Root<StudentCourseGrade> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            List<Predicate> conditions = new ArrayList<>();
            if (!StringUtils.isAllBlank(academyName, className, studentName, courseName, teacherId, year) || null != termType || null != isCorrect) {
                if (StringUtils.isNotBlank(academyName)) {
                    conditions.add(cb.and(cb.like(root.get("academyName").as(String.class), "%" + academyName + "%")));
                }
                if (StringUtils.isNotBlank(className)) {
                    conditions.add(cb.and(cb.like(root.get("className").as(String.class), "%" + className + "%")));
                }
                if (StringUtils.isNotBlank(studentName)) {
                    conditions.add(cb.and(cb.like(root.get("studentName").as(String.class), "%" + studentName + "%")));
                }
                if (StringUtils.isNotBlank(courseName)) {
                    conditions.add(cb.and(cb.like(root.get("courseName").as(String.class), "%" + courseName + "%")));
                }
                if (StringUtils.isNotBlank(teacherId)) {
                    Join<StudentCourseGrade, StudentCourse> studentCourseJoin = root.join("studentCourse", JoinType.LEFT);
                    Join<StudentCourse, CourseTeacher> courseTeacherJoin = studentCourseJoin.join("courseTeacher", JoinType.LEFT);
                    Join<CourseTeacher, Teacher> teacherJoin = courseTeacherJoin.join("teacher", JoinType.LEFT);
                    conditions.add(cb.and(cb.equal(teacherJoin.get("id").as(Long.class), Long.valueOf(teacherId))));
                }
                if (StringUtils.isNotBlank(year)) {
                    conditions.add(cb.and(cb.equal(root.get("year").as(Integer.class), Integer.valueOf(year))));
                }
                if (null != termType) {
                    conditions.add(cb.and(cb.equal(root.get("termType").as(TermType.class), termType)));
                }
                if (null != isCorrect) {
                    conditions.add(cb.and(cb.equal(root.get("isCorrect").as(Boolean.class), isCorrect)));
                }
            }
            if (!conditions.isEmpty()) {
                query.where(conditions.toArray(new Predicate[0]));
            }
            return null;
        });
        Page<StudentCourseGrade> all = this.studentCourseGradeRepository.findAll(specification, PageHelper.generatePageRequest(page, size, sort));
        List<StudentCourseGradeDTO> dtos = this.studentCourseGradeMapper.toList(all.getContent());
        return PageDataUtil.toPageData(all, dtos);
    }

    /**
     * 提交批改的学生成绩
     *
     * @param id                    学生课程成绩id
     * @param studentCourseGradeDTO 学生课程成绩
     * @return 学生课程成绩
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public StudentCourseGradeDTO complete(String id, StudentCourseGradeDTO studentCourseGradeDTO) {
        StudentCourseGrade studentCourseGrade = this.getOne(id);
        this.studentCourseGradeMapper.updateEntity(studentCourseGradeDTO, studentCourseGrade);
        this.studentCourseGradeRepository.save(studentCourseGrade);
        // 将选课信息的修读状态设置为结课，并保存
        StudentCourse studentCourse = studentCourseGrade.getStudentCourse();
        studentCourse.setStudyStatus(StudyStatus.END_CLASS);
        this.studentCourseRepository.save(studentCourse);

        // 录入学分到学生学分记录
        this.recordCredits(studentCourseGrade);

        // 判断该成绩所处学年、学期是否存在汇总数据
        // 根据学生code、年度、学期查询成绩汇总数据，并把该成绩添加到汇总
        List<StudentCourseGradeGatherDTO> gradeGatherDTOS = this.studentCourseGradeGatherService.search(studentCourseGrade.getStudentCourse().getStudent().getCode(), studentCourseGrade.getYear().toString(), studentCourseGrade.getTermType());
        if (gradeGatherDTOS != null && !gradeGatherDTOS.isEmpty()) {
            // 已存在本学年学期的汇总数据，添加到汇总，保存
            StudentCourseGradeGatherDTO studentCourseGradeGatherDTO = gradeGatherDTOS.get(0);
            StudentCourseGradeGather studentCourseGradeGather = this.studentCourseGradeGatherMapper.toEntity(studentCourseGradeGatherDTO);
            studentCourseGrade.setStudentCourseGradeGather(studentCourseGradeGather);
            this.studentCourseGradeRepository.save(studentCourseGrade);
        } else {
            // 尚未存在本学年学期的汇总数据，新建汇总
            StudentCourseGradeGather studentCourseGradeGather = new StudentCourseGradeGather();
            studentCourseGradeGather.setYear(studentCourseGrade.getYear());
            studentCourseGradeGather.setTermType(studentCourseGrade.getTermType());
            studentCourseGradeGather.setAcademyName(studentCourseGrade.getAcademyName());
            studentCourseGradeGather.setClassName(studentCourseGrade.getClassName());
            studentCourseGradeGather.setAverageCreditGradePoint(studentCourseGrade.getCreditGradePoint());
            studentCourseGradeGather.setStudentCode(studentCourseGrade.getStudentCode());
            studentCourseGradeGather.setStudentName(studentCourseGrade.getStudentName());
            this.studentCourseGradeGatherRepository.save(studentCourseGradeGather);
            // 学生成绩添加到汇总
            studentCourseGrade.setStudentCourseGradeGather(studentCourseGradeGather);
            this.studentCourseGradeRepository.save(studentCourseGrade);
        }
        return this.studentCourseGradeMapper.toDTO(studentCourseGrade);
    }

    /**
     * 模糊查询学生课程成绩
     *
     * @param studentCode 学生学号
     * @param year  学年
     * @param termType 学期
     * @return 学生课程成绩
     */
    @Override
    public List<StudentCourseGradeDTO> search(String studentCode, String year, TermType termType) {
        PredicateBuilder<StudentCourseGrade> pb = Specifications.<StudentCourseGrade>and()
                .eq(StringUtils.isNotBlank(studentCode), "studentCode", studentCode)
                .eq(StringUtils.isNotBlank(year), "year", year)
                .eq(null != termType, "termType", termType);
        List<StudentCourseGrade> all = this.studentCourseGradeRepository.findAll(pb.build());
        return this.studentCourseGradeMapper.toList(all);
    }

    /**
     * 通过id查找学生课程成绩
     *
     * @param id 学生课程成绩id
     * @return 学生课程成绩
     */
    @Override
    public StudentCourseGradeDTO findById(String id) {
        StudentCourseGrade studentCourseGrade = this.getOne(id);
        CourseType courseType = studentCourseGrade.getStudentCourse().getCourseTeacher().getCourse().getCourseType();
        StudentCourseGradeDTO studentCourseGradeDTO = this.studentCourseGradeMapper.toDTO(studentCourseGrade);
        String courseTypeName = courseType.getName();
        if(courseType.getCourseBaseType() != null){
            if("REQUIRED_COURSE".equals(courseType.getCourseBaseType().toString())){
                courseTypeName = courseTypeName + "/必修课";
            } else if ("OPTIONAL_COURSE".equals(courseType.getCourseBaseType().toString())) {
                courseTypeName = courseTypeName + "/任选课";
            } else{
                courseTypeName = courseTypeName + "/限选课";
            }
        }
        studentCourseGradeDTO.setCourseType(courseTypeName);
        return studentCourseGradeDTO;
    }

    /**
     * 根据学生id查找学生所有课程成绩
     *
     * @param studentId 学生id
     * @return 学生所有课程成绩
     */
    @Override
    public List<StudentCourseGradeDTO> findAllByStudentId(String studentId) {
        Specification<StudentCourseGrade> specification = ((Root<StudentCourseGrade> root, CriteriaQuery<?> query, CriteriaBuilder cb) -> {
            List<Predicate> conditions = new ArrayList<>();
            if (StringUtils.isNotBlank(studentId)) {
                Join<StudentCourseGrade, StudentCourse> studentCourseJoin = root.join("studentCourse", JoinType.LEFT);
                Join<StudentCourse, Student> studentJoin = studentCourseJoin.join("student", JoinType.LEFT);
                conditions.add(cb.and(cb.equal(studentJoin.get("id").as(Long.class), Long.valueOf(studentId))));
            }
            if (!conditions.isEmpty()) {
                query.where(conditions.toArray(new Predicate[0]));
            }
            return null;
        });
        List<StudentCourseGrade> all = this.studentCourseGradeRepository.findAll(specification);
        return this.studentCourseGradeMapper.toList(all);
    }

    /**
     * 修改已批改的学生成绩
     *
     * @param id                    学生课程成绩id
     * @param studentCourseGradeDTO 学生课程成绩
     * @return 学生课程成绩
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public StudentCourseGradeDTO update(String id, StudentCourseGradeDTO studentCourseGradeDTO) {
        StudentCourseGrade studentCourseGrade = this.getOne(id);
        this.studentCourseGradeMapper.updateEntity(studentCourseGradeDTO, studentCourseGrade);
        this.studentCourseGradeRepository.save(studentCourseGrade);
        return this.studentCourseGradeMapper.toDTO(studentCourseGrade);
    }

    /**
     * 根据id获取学生课程成绩
     *
     * @param id 学生课程成绩id
     * @return 学生课程成绩
     */
    private StudentCourseGrade getOne(String id) {
        Optional<StudentCourseGrade> studentCourseGrade = this.studentCourseGradeRepository.findById(Long.valueOf(id));
        if (!studentCourseGrade.isPresent()) {
            throw new NoSuchDataException(id);
        }
        return studentCourseGrade.get();
    }

    /**
     * 录入学分到学生学分记录
     *
     * @param studentCourseGrade 学生课程成绩
     */
    private void recordCredits(StudentCourseGrade studentCourseGrade) {
        StudentCreditRecord studentCreditRecord = Optional
                .ofNullable(this.studentCreditRecordRepository.findByStudentCode(studentCourseGrade.getStudentCode()))
                .orElse(new StudentCreditRecord());
        if (studentCreditRecord.getId() == null) {
            // 是第一次记录，则新建记录数据
            studentCreditRecord.setAcademyName(studentCourseGrade.getAcademyName());
            studentCreditRecord.setClassName(studentCourseGrade.getClassName());
            studentCreditRecord.setStudentCode(studentCourseGrade.getStudentCode());
            studentCreditRecord.setStudentName(studentCourseGrade.getStudentName());
            studentCreditRecord.setGender(studentCourseGrade.getStudentCourse().getStudent().getGender());
            studentCreditRecord.setGrade(studentCourseGrade.getStudentCourse().getStudent().getGrade());
            studentCreditRecord.setTotal(0.0);
            studentCreditRecord.setPublicElective(0.0);
            studentCreditRecord.setProfessionElective(0.0);
            studentCreditRecord.setCompulsoryCredit(0.0);
            studentCreditRecord.setPracticeCredit(0.0);
            studentCreditRecord.setSubjectBasedElective(0.0);
            studentCreditRecord.setGraduationInternship(0.0);
            studentCreditRecord.setGraduationThesis(0.0);
            studentCreditRecord.setInnovationScore(0.0);
            // 根据课程类型，计算修得学分
            this.setGetCredit(studentCreditRecord, studentCourseGrade);
            // 关联学生毕业学分最低要求
            AcademicProgress academicProgress = this.academicProgressRepository.findByStudentCode(studentCourseGrade.getStudentCode());
            studentCreditRecord.setAcademicProgress(academicProgress);
            // 保存
            studentCreditRecordRepository.save(studentCreditRecord);
        } else {
            // 已存在历史记录，则录入新记录
            // 根据课程类型，计算修得学分
            this.setGetCredit(studentCreditRecord, studentCourseGrade);
            // 计算总分
            studentCreditRecord.setTotal(studentCreditRecord.getTotal() + studentCourseGrade.getGetCredit());
            // 保存
            studentCreditRecordRepository.save(studentCreditRecord);
        }
    }

    /**
     * 根据课程类型，计算修得学分
     *
     * @param studentCreditRecord 学生学业学分记录
     * @param studentCourseGrade  学生课程成绩
     */
    private void setGetCredit(StudentCreditRecord studentCreditRecord, StudentCourseGrade studentCourseGrade) {
        int i = this.filterType(studentCourseGrade.getCourseType());
        switch (i) {
            case 1:
                Double compulsoryCredit = studentCreditRecord.getCompulsoryCredit();
                if (compulsoryCredit != null && compulsoryCredit >= 0) {
                    studentCreditRecord.setCompulsoryCredit(compulsoryCredit + studentCourseGrade.getGetCredit());
                } else {
                    studentCreditRecord.setCompulsoryCredit(studentCourseGrade.getGetCredit());
                }
                break;
            case 2:
                Double professionElective = studentCreditRecord.getProfessionElective();
                if (professionElective != null && professionElective >= 0) {
                    studentCreditRecord.setProfessionElective(professionElective + studentCourseGrade.getGetCredit());
                } else {
                    studentCreditRecord.setProfessionElective(studentCourseGrade.getGetCredit());
                }
                break;
            case 3:
                Double subjectBasedElective = studentCreditRecord.getSubjectBasedElective();
                if (subjectBasedElective != null && subjectBasedElective >= 0) {
                    studentCreditRecord.setSubjectBasedElective(subjectBasedElective + studentCourseGrade.getGetCredit());
                } else {
                    studentCreditRecord.setProfessionElective(studentCourseGrade.getGetCredit());
                }
                break;
            case 4:
                Double publicElective = studentCreditRecord.getPublicElective();
                if (publicElective != null && publicElective >= 0) {
                    studentCreditRecord.setPublicElective(publicElective + studentCourseGrade.getGetCredit());
                } else {
                    studentCreditRecord.setPublicElective(studentCourseGrade.getGetCredit());
                }
                break;
            case 5:
                Double practiceCredit = studentCreditRecord.getPracticeCredit();
                if (practiceCredit != null && practiceCredit >= 0) {
                    studentCreditRecord.setPracticeCredit(practiceCredit + studentCourseGrade.getGetCredit());
                } else {
                    studentCreditRecord.setPracticeCredit(studentCourseGrade.getGetCredit());
                }
                break;
            default:
        }
    }

    /**
     * 过滤出课程类型
     *
     * @param courseType 课程类型
     * @return
     */
    private int filterType(String courseType) {
        if (courseType.contains("必修课")) {
            // 必修学分：所有必修课
            return 1;
        } else if (courseType.contains("专业课")) {
            // 专业选修：专业课/限选课 + 专业课/任选课
            return 2;
        } else if (courseType.contains("学科基础课")) {
            // 学科基础选修: 学科基础课/限选课
            return 3;
        } else if (courseType.contains("公共课")) {
            // 公共选修：公共课/任选课
            return 4;
        } else {
            // 实践学分
            return 5;
        }
    }
}